using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Lucene.Net.Documents;
using Lucene.Net.Index;

namespace LuceneSearch.ContactApp.Indexing
{
    public class EntityIndexWriter : IEntityIndexProcessor
    {
        private readonly IndexDirectoryProvider directoryProvider;
        private readonly AnalyzerFactory analyzerFactory;

        public EntityIndexWriter(IndexDirectoryProvider directoryProvider, AnalyzerFactory analyzerFactory)
        {
            this.directoryProvider = directoryProvider;
            this.analyzerFactory = analyzerFactory;
        }

        public bool CanProcess(IndexWorkType indexWorkType)
        {
            return indexWorkType == IndexWorkType.Add;
        }

        public void Process(IEnumerable<object> entities)
        {
            var typeGroups = entities.GroupBy(x => x.GetType());

            foreach (var typeGroup in typeGroups)
            {
                IndexEntitesOfSameType(typeGroup);
            }
        }

        private void IndexEntitesOfSameType(IGrouping<Type, object> typeGroup)
        {
            IndexWriter writer = null;
            try
            {
                var directory = directoryProvider.GetIndexDirectoryForType(typeGroup.Key);
                var exists = IndexReader.IndexExists(directory);

                writer = new IndexWriter(directory, analyzerFactory.Create(), !exists);

                foreach (var entity in typeGroup)
                {
                    IndexEntity(writer, entity);                        
                }
            }
            finally
            {
                if (writer != null)
                {
                    writer.Optimize();
                    writer.Close();
                }
            }
        }

        private static void IndexEntity(IndexWriter writer, object entity)
        {
            var doc = new Document();

            IndexFields(entity, doc);

            writer.AddDocument(doc);
        }

        private static void IndexFields(object entity, Document doc)
        {
            var properties = entity.GetType().GetProperties();

            foreach (var prop in properties)
            {
                var isId = prop.Name == "Id";

                AddFieldToDocument(prop, doc, entity.GetIndexValue(prop.Name), isId, !isId);
            }
        }

        private static void AddFieldToDocument(PropertyInfo prop, Document doc, string indexedValue, bool store, bool tokenize)
        {
            doc.Add(new Field(prop.Name, indexedValue,
                store ? Field.Store.YES : Field.Store.NO, 
                tokenize ? Field.Index.TOKENIZED : Field.Index.UN_TOKENIZED));
        }
    }
}